/*
 * InodeBlocksAdder.cpp
 *
 *  Created on: May 12, 2010
 *      Author: Lukasz Spintzyk
 */

#include "InodeBlocksAdder.h"
#include "SuperBlock.h"
#include "BlockReader.h"

InodeBlocksAdder::InodeBlocksAdder(BlockManager& reader, SuperBlock& sb)
	:d_buffer(new byte[8*1024*3])// TODO parametrize the buffer length
	,d_buffer2(new byte[8*1024*3])
	,d_buffer3(new byte[8*1024*3])
	,d_reader(reader)
	,d_sb(sb)
{
}

/*****************************************************************************/
/*****************************************************************************/

InodeBlocksAdder::~InodeBlocksAdder()
{
}

/*****************************************************************************/
/*****************************************************************************/

void InodeBlocksAdder::addBlocksToInode(InodePtr inode, uint32_t free_block, uint32_t num)
{
	// TODO test function
		uint32_t size = num;

		uint32_t size_new = addDirectBlocks(inode, free_block, size);
		free_block+=size-size_new;
		size = size_new;

		if (size)
		{
			size_new = addOneIndirectBlocks(inode, free_block, size);
			free_block+=size-size_new;
			size = size_new;
		}

		if ( size )
		{
			size_new = addTwoIndirectBlocks(inode, free_block, size);
			free_block+=size-size_new;
			size = size_new;
		}

		if ( size )
		{
			addThreeIndirectBlocks(inode, free_block, size);
		}

}


/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeBlocksAdder::addDirectBlocks(InodePtr inode, uint32_t free_block, uint32_t size)
{
	uint32_t direct_blocks = DIRECT_LAST_INDEX +1;

	while ( (inode->d_blocks < direct_blocks) &&
			(size)
			)
	{
		inode->d_block[inode->d_blocks++] = free_block++;
		size--;
	}
	return size;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeBlocksAdder::addToBlock(uint32_t block, uint32_t free_block, uint32_t size, uint32_t where)
{
	uint32_t* inodes =(uint32_t*) d_buffer;
	d_reader.getBlock(block, d_buffer);
	uint32_t sizeB = d_sb.d_blockSize/sizeof(uint32_t);

	assert( ( (where>0) && (inodes[where-1]!=0) ) || (where==0));
	uint32_t i = where;
	while ( (i < sizeB) && size )
	{
		if (inodes[i] == 0)
		{
			inodes[i] = free_block++;
			size--;
		}
		i++;
	}

	d_reader.setBlock(block, d_buffer);
	return size;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeBlocksAdder::addToIndirectBlock(
		uint32_t block,
		uint32_t free_block,
		uint32_t size,
		uint32_t indirect_block,
		uint32_t direct_block,
		uint32_t& added_non_leaf_block)
{
	uint32_t* inodes2 =(uint32_t*) d_buffer2;

	d_reader.getBlock(block, d_buffer2);
	uint32_t sizeB = d_sb.d_blockSize/sizeof(uint32_t);
	uint32_t new_size =0;

	uint32_t i =indirect_block;
	while ((i < sizeB) && size)
	{

		if (inodes2[i] == 0)
		{
			inodes2[i] = free_block++;
			size--;
			added_non_leaf_block++;
			memset(d_buffer, 0, d_sb.d_blockSize);
			d_reader.setBlock(inodes2[i], d_buffer);
			d_reader.setBlock(block, d_buffer2);
			continue;
		}


		new_size = addToBlock(inodes2[i], free_block, size, direct_block);
		direct_block = 0;
		free_block+= size - new_size;
		size = new_size;

		i++;
	}
	return size;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeBlocksAdder::addToDoubleIndirectBlock(
		uint32_t block,
		uint32_t free_block,
		uint32_t size,
		uint32_t block1,
		uint32_t block2,
		uint32_t entry,
		uint32_t& added_non_leaf_block)
{
	uint32_t* inodes3 =(uint32_t*) d_buffer3;

	d_reader.getBlock(block, d_buffer3);
	uint32_t sizeB = d_sb.d_blockSize/sizeof(uint32_t);
	uint32_t new_size =0;

	uint32_t i = block1;
	while ((i < sizeB) && size)
	{

		if (inodes3[i] == 0)
		{
			inodes3[i] = free_block++;
			size--;
			added_non_leaf_block++;
			memset(d_buffer, 0, d_sb.d_blockSize);
			d_reader.setBlock(inodes3[i], d_buffer);
			d_reader.setBlock(block, d_buffer3);
			continue;
		}


		new_size = addToIndirectBlock(inodes3[i], free_block, size, block2, entry, added_non_leaf_block);
		block2 = 0;
		entry = 0;
		free_block+= size - new_size;
		size = new_size;

		i++;
	}
	return size;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeBlocksAdder::addOneIndirectBlocks(InodePtr inode, uint32_t free_block, uint32_t size)
{
	if (size == 0)
	{
		return size;
	}
	uint32_t direct_blocks = DIRECT_LAST_INDEX +1;
	uint32_t indirect_blocks = d_sb.d_blockSize/sizeof(uint32_t);

	uint32_t blocks = direct_blocks + indirect_blocks;

	uint32_t new_size = 0;

	if (inode->d_blocks > blocks)
	{
		return size;
	}


	if (!inode->d_block[INDIRECT_1_INDEX])
	{
		size--;
		inode->d_block[INDIRECT_1_INDEX] = free_block++;
		memset(d_buffer, 0, d_sb.d_blockSize);
		d_reader.setBlock(inode->d_block[INDIRECT_1_INDEX], d_buffer);
		return addOneIndirectBlocks(inode, free_block, size);
	}
	assert(inode->d_blocks>=direct_blocks);
	uint32_t where = inode->d_blocks - direct_blocks;


	new_size = addToBlock(inode->d_block[INDIRECT_1_INDEX], free_block, size, where);
	inode->d_blocks += size - new_size;

	return new_size;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeBlocksAdder::addTwoIndirectBlocks(InodePtr inode, uint32_t free_block, uint32_t size)
{
	if (!size)
	{
		return size;
	}

	if (!inode->d_block[INDIRECT_2_INDEX])
	{
		size--;
		inode->d_block[INDIRECT_2_INDEX] = free_block++;
		memset(d_buffer, 0, d_sb.d_blockSize);
		d_reader.setBlock(inode->d_block[INDIRECT_2_INDEX], d_buffer);
		return addTwoIndirectBlocks(inode, free_block, size);
	}


	uint32_t direct_blocks = DIRECT_LAST_INDEX +1;
	uint32_t indirect_blocks = d_sb.d_blockSize/sizeof(uint32_t);
	uint32_t double_indirect = indirect_blocks*indirect_blocks;
	uint32_t blocks = direct_blocks + indirect_blocks + double_indirect;

	if (inode->d_blocks > blocks)
	{
		return size;
	}

	uint32_t where = inode->d_blocks - direct_blocks - indirect_blocks;
	uint32_t indirect = where/indirect_blocks;
	uint32_t direct = where%indirect_blocks;


	uint32_t added_to_non_leaf_block=0;
	uint32_t new_size =
			addToIndirectBlock(inode->d_block[INDIRECT_2_INDEX],
								free_block,
								size,
								indirect,
								direct,
								added_to_non_leaf_block);
	inode->d_blocks += size - new_size - added_to_non_leaf_block;
	return new_size;
}

/*****************************************************************************/
/*****************************************************************************/

uint32_t InodeBlocksAdder::addThreeIndirectBlocks(InodePtr inode, uint32_t free_block, uint32_t size)
{
	if (!size)
	{
		return size;
	}

	if (!inode->d_block[INDIRECT_3_INDEX])
	{
		size--;
		inode->d_block[INDIRECT_3_INDEX] = free_block++;
		memset(d_buffer, 0, d_sb.d_blockSize);
		d_reader.setBlock(inode->d_block[INDIRECT_3_INDEX], d_buffer);
		return addThreeIndirectBlocks(inode, free_block, size);
	}


	uint32_t direct_blocks = DIRECT_LAST_INDEX +1;
	uint32_t indirect_blocks = d_sb.d_blockSize/sizeof(uint32_t);
	uint32_t double_indirect = indirect_blocks * indirect_blocks;
	uint32_t triple_indirect = double_indirect * indirect_blocks;
	uint32_t blocks = direct_blocks + indirect_blocks + double_indirect + triple_indirect;

	if (inode->d_blocks > blocks)
	{
		return size;
	}

	uint32_t where = inode->d_blocks - direct_blocks - indirect_blocks - double_indirect;

	uint32_t block1 = where / (indirect_blocks * indirect_blocks);
	where = (where - block1 * indirect_blocks * indirect_blocks) ;
	uint32_t block2 = where / indirect_blocks;
	uint32_t entry = where % indirect_blocks;

	uint32_t added_to_non_leaf_block = 0;
	uint32_t new_size =
			addToDoubleIndirectBlock (
					inode->d_block[INDIRECT_3_INDEX],
					free_block,
					size,
					block1,
					block2,
					entry,
					added_to_non_leaf_block);
	inode->d_blocks += size - new_size - added_to_non_leaf_block;

	return new_size;
}
